package com.example.myapplication

import android.content.Context
import org.web3j.crypto.*
import java.io.File

/**
 * @author Quinn
 * @date 2022/9/5
 * @Desc
 */


class Web3Util {

    companion object {
        val instance: Web3Util by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Web3Util()
        }
    }

    private val myWallet = mutableListOf<String>()

    private lateinit var storeManager: StoreManager

    fun init(context: Context, userId: String) {
        storeManager = StoreManager().apply { init(context) }
        loadStore(userId)
    }

    /**
     * Taking the user as the dimension, create different files to store real password , mnemonic , user pwd respectively
     */
    fun createWallet(userId: String, password: String, reset: Boolean = false) {
        try {
            // To reset the wallet, a new wallet file needs to be generated, and the user can also undo the reset
            // Therefore, the original store needs to be backed up first
            if (reset) storeManager.reset(userId)
            with(MD5Utils.encode("${System.currentTimeMillis()}_${MD5Utils.encode(userId)}")) {
                WalletUtils
                    .generateBip39Wallet(
                        this,
                        storeManager.keystoreFileParent(userId)
                    ).let { wallet ->
                        memoryCacheStore(password, this, wallet.mnemonic, wallet.filename).let {
                            storeManager.diskCacheStore(it, userId)
                        }

                    }
            }
        } catch (e: Exception) {
            println("create wallet error ${e.printStackTrace()}")
        }
    }


     fun createBip44Wallet(pwd: String, destinationDirectory: File): Bip39Wallet {
        val initialEntropy = ByteArray(16)
        SecureRandomUtils.secureRandom().nextBytes(initialEntropy)

        val mnemonic = MnemonicUtils.generateMnemonic(initialEntropy)
        val seed = MnemonicUtils.generateSeed(mnemonic, null)

        // m/44'/77777'/0'/0/0
        val masterKeypair = Bip32ECKeyPair.generateKeyPair(seed)
        val path = intArrayOf(
            44 or Bip32ECKeyPair.HARDENED_BIT,
            77777 or Bip32ECKeyPair.HARDENED_BIT,
            0 or Bip32ECKeyPair.HARDENED_BIT,
            0,
            0
        )
        val bip44Keypair = Bip32ECKeyPair.deriveKeyPair(masterKeypair, path)
        val walletName =
            WalletUtils.generateWalletFile(pwd, bip44Keypair, destinationDirectory, false)

        return Bip39Wallet(walletName, mnemonic)
    }

     fun generateBip44WalletFileByMnemonic(
        pwd: String,
        mnemonic: String,
        destinationDirectory: File
    ): Bip39Wallet {

        val seed = MnemonicUtils.generateSeed(mnemonic, null)
        // m/44'/60'/0'/0/0
        val masterKeypair = Bip32ECKeyPair.generateKeyPair(seed)
        val path = intArrayOf(
            44 or Bip32ECKeyPair.HARDENED_BIT,
            7777 or Bip32ECKeyPair.HARDENED_BIT,
            0 or Bip32ECKeyPair.HARDENED_BIT,
            0,
            0
        )
        val bip44Keypair = Bip32ECKeyPair.deriveKeyPair(masterKeypair, path)
        val filename =
            WalletUtils.generateWalletFile(pwd, bip44Keypair, destinationDirectory, false)
        return Bip39Wallet(filename, mnemonic)
    }





    /**
     * regenerate wallet file with mnemonic
     */
    fun restoreWalletByMnemonic(
        password: String,
        mnemonic: String,
        userId: String
    ) {
        if (MnemonicUtils.validateMnemonic(mnemonic)) {
            try {
                with(MD5Utils.encode("${System.currentTimeMillis()}_$userId")) {
                    WalletUtils.generateBip39WalletFromMnemonic(
                        this,
                        mnemonic,
                        storeManager.keystoreFileParent(userId)
                    ).let { wallet ->
                        storeManager.diskCacheStore(
                            memoryCacheStore(
                                password,
                                this,
                                wallet.mnemonic,
                                wallet.filename
                            ),
                            userId
                        )
                    }
                }
            } catch (e: Exception) {
                println("restore wallet error from mnemonic")
            }
        } else {
            // mnemonic is invalidate
            println("Mnemonic is error")
        }
    }

    fun undoResetStore(userId: String) {
        takeIf { hasOrgWallet(userId) }?.apply { storeManager.revert(userId) }?.loadStore(userId)
    }

    /**
     * determine if there is a wallet
     * 1. whether the local keystore exists
     * 2. matches the server public key
     *
     * judgment process：
     * First obtain the keystore file name by reading the local target; determine whether the file exists
     * Read the keystore public key and compare it with the server。
     */
    fun hasWallet(userId: String, address: String) =
        availableWallet()?.let {
            val realPwd = it[1]
            val keystoreName = it[3]
            val loadCredential =
                loadCredential(
                    realPwd,
                    storeManager.keystoreFilePath(userId, keystoreName)
                )
            address == loadCredential.address
        } ?: false

    private fun hasOrgWallet(userId: String) = storeManager.hasOrgStore(userId)

    /**
     * unlockWallet
     * Verify the validity of the password through the password of web2
     */
    fun unlockWallet(password: String) =
        myWallet.takeIf { it.size == 4 }?.get(0) == generateWeb2Password(password)


    fun changeWalletPassword(userId: String, newPassword: String) =
        availableWallet()?.apply {
            this[0] = generateWeb2Password(newPassword)
            storeManager.diskCacheStore(this, userId)
            return true
        } ?: false


    /**
     * loading Credentials
     */
    private fun loadCredential(password: String, keystorePath: String) =
        WalletUtils.loadCredentials(password, keystorePath)


    //================================== basic function ===================================


    private fun signMessage(userId: String, msg: String): Sign.SignatureData? {
        return Sign.signMessage(msg.toByteArray(), loadCredential(userId, "").ecKeyPair)
    }


    private fun generateWeb2Password(password: String) = MD5Utils.encode(password)

    private fun loadStore(userId: String) = myWallet.addAll(storeManager.loadStore(userId))

    private fun availableWallet(): MutableList<String>? = myWallet.takeIf { it.size >= 4 }

    private fun memoryCacheStore(pwd: String, rPwd: String, mno: String, kpn: String) =
        myWallet.apply {
            clear()
            add(generateWeb2Password(pwd))
            add(rPwd)
            add(mno)
            add(kpn)
        }

}